package lib.gen;

import java.util.ArrayList;

import lib.util.ArraysLong;

/**
 * Deals with generating sequences
 * @author dolphinigle
 */
public abstract class Sequence {

  /**
   * Generates a sorted sequence of longs that forms an exponential progression
   * <br/><br/>
   * Will try to generate exactly max_n elements, but if failed due to
   * violating bounds it exits
   * @param first_number first number in the sequence
   * @param multiplier next number = last number * multiplier
   * @param lower_bound minimum allowed vlaue
   * @param upper_bound maximum allowed value
   * @param max_n maximum number of elements
   * @return an array of long
   */
  public static long[] generateExponentialSequenceLong(
          long first_number, long multiplier, long lower_bound,
          long upper_bound, int max_n) {
    assert max_n >= 1;
    assert lower_bound <= upper_bound;
    ArrayList<Long> ret = new ArrayList<Long>();
    ret.add(first_number);

    for (int i = 1; i < max_n; ++i) {
      if (ret.get(ret.size() - 1) > upper_bound / multiplier) break;
      long testnum = ret.get(ret.size() - 1) * multiplier;
      if (ret.get(ret.size() - 1) == 0L) ++testnum;
      if (testnum > upper_bound) break;
      if (testnum < lower_bound) break;
      ret.add(testnum);
    }

    long[] res = new long[ret.size()];
    for (int i = 0; i < ret.size(); ++i) {
      res[i] = ret.get(i);
    }

    return res;
  }

  /**
   * Forms a fibonacci sequence with arbitrary starting numbers
   * <br/>
   * <br/>
   * I.e., F[x] = F[x-2] + F[x-1] for all x. Strives to achieve max_n elements,
   * but will yield if any exceeds the bounds
   * @param first_number F0
   * @param second_number F1
   * @param lower_bound minimum possible value
   * @param upper_bound maximum possible value
   * @param max_n maximum number of elements
   * @return an array of long
   */
  public static long[] generateFibonacciLong(
          long first_number, long second_number,
          long lower_bound,
          long upper_bound, int max_n) {
    assert (max_n >= 0);

    ArrayList<Long> ret = new ArrayList<Long>();

    if (max_n == 0L || first_number > upper_bound || first_number < lower_bound)
      return new long[0];
    
    ret.add(first_number);
    
    if (max_n == 1L || second_number > upper_bound || second_number < lower_bound) {
      long[] rest = new long[1];
      rest[0] = first_number;
      return rest;
    }

    ret.add(second_number);

    for (int i = 0; i < max_n - 2; ++i) {
      long newnumber = ret.get(ret.size() - 2) + ret.get(ret.size() - 1);
      if (newnumber > upper_bound) break;
      if (newnumber < lower_bound) break;
      ret.add(newnumber);
    }

    long[] res = new long[ret.size()];
    for (int i = 0; i < ret.size(); ++i) {
      res[i] = ret.get(i);
    }
    return res;
  }

  /**
   * Forms an arithmetic sequence.
   * 
   * x + i*dx, for i starting in 0.
   * 
   * @param x
   * @param dx
   * @param lower_bound minimum possible value of the elements returned.
   * @param upper_bound maximum possible value of the elements returned.
   * @param max_n maximum number of elements returend.
   */
  public static long[] ArithmeticSequence(long x, long dx,
      long lower_bound, long upper_bound, int max_n) {
    assert max_n >= 0;
    ArrayList<Long> ret = new ArrayList<Long>();
    for (long i = 0; ; ++i) {
      long number = x + i * dx;
      if (number < lower_bound || number > upper_bound) break;
      if (ret.size() >= max_n) break;
      ret.add(number);
    }
    return ArraysLong.ArrayListLongToArray(ret);
  }
  
  /**
   * Similar to Python's range()
   */
  public static int[] range(int n) {
    assert n >= 0;
    int[] ret = new int[n];
    for (int i = 0; i < n; ++i) ret[i] = i;
    return ret;
  }
  
  /**
   * Similar to Python's range()
   * @param low
   * @param hi
   * @return
   */
  public static int[] range(int low, int hi) {
    if (hi <= low) return new int[0];
    int[] ret = new int[hi - low];
    for (int i = low; i < hi; ++i) {
      ret[i - low] = i;
    }
    return ret;
  }
}
